/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.common.crypto.digest;

import org.vacoor.nothing.common.codec.Base64;
import org.vacoor.nothing.common.codec.Hex;
import org.vacoor.nothing.common.util.Bytes;
import org.vacoor.nothing.common.util.Throwables;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * Hash-based Message Authentication Code
 * <p/>
 * new Hmac.HmacMD5(keyBytes, "message").toHex();
 *
 * @author vacoor
 */
@SuppressWarnings({"unused"})
public class Hmac {

    public enum Algorithm {
        HMAC_MD5("HmacMD5"), HMAC_SHA1("HmacSHA1"), HMAC_SHA256("HmacSHA256"), HMAC_SHA384("HmacSHA384"), HMAC_SHA512("HmacSHA512");

        private static final int DEFAULT_KEY_SIZE = 1024;
        private final String name;

        Algorithm(String algorithm) {
            this.name = algorithm;
        }

        public byte[] generateKey() {
            return generateKey(DEFAULT_KEY_SIZE);
        }

        // 生成密钥
        public byte[] generateKey(int keySize) {
            try {
                KeyGenerator keyGen = KeyGenerator.getInstance(name);
                keyGen.init(keySize);
                return keyGen.generateKey().getEncoded();
            } catch (NoSuchAlgorithmException e) {
                throw Throwables.unchecked(e);
            }
        }
    }

    /**
     * HMAC-MD5
     */
    public static class HmacMD5 extends Hmac {

        public HmacMD5(byte[] key, Object source) {
            super(Algorithm.HMAC_MD5, key, source);
        }
    }

    /**
     * HMAC-SHA1
     */
    public static class HmacSHA1 extends Hmac {

        public HmacSHA1(byte[] key, Object source) {
            super(Algorithm.HMAC_SHA1, key, source);
        }

    }

    private byte[] bytes;   // hashed bytes
    private String hexEncoded;
    private String base64Encoded;

    public Hmac(Algorithm algorithm, byte[] key, Object source) {
        if (algorithm == null || key == null || source == null) {
            throw new NullPointerException("algorithm, key and source argument cannot be null.");
        }

        hash(algorithm, key, Bytes.toBytes(source));
    }

    protected void hash(Algorithm algorithm, byte[] key, byte[] source) {
        try {
            bytes = hash(algorithm.name, key, source);
        } catch (GeneralSecurityException e) {
            throw Throwables.unchecked(e);
        }
    }

    protected byte[] hash(String algorithm, byte[] key, byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, algorithm);

        Mac mac = Mac.getInstance(algorithm);
        mac.init(secretKeySpec);
        mac.reset();
        return mac.doFinal(bytes);
    }

    public byte[] getBytes() {
        return bytes;
    }

    public String toHex() {
        if (null == hexEncoded) {
            hexEncoded = Hex.encode(bytes);
        }
        return hexEncoded;
    }

    public String toBase64() {
        if (null == base64Encoded) {
            base64Encoded = Base64.encodeToString(bytes);
        }
        return base64Encoded;
    }
}
